const assert = require('assert')

module.exports = class LogFetcher {
  constructor(options) {
    this.offset = options.offset
    this.fetchLimit = options.fetchLimit || 200
    this.contract = options.contract
    this.tronWeb = options.tronWeb
    this.repo = options.repo
    this._haveRollback = false
    this._fetchCount = 0
  }

  get haveRollback() {
    return this._haveRollback
  }

  get fetchCount() {
    return this._fetchCount
  }

  async fetch() {
    // step1: diff remote and local logs, rollback
    this._haveRollback = false
    this._fetchCount = 0
    let offset = Math.max(0, this.offset - this.fetchLimit)
    if (offset < this.offset) {
      while (true) {
        const remoteOldLogs = await this._fetchRemoteLogs(offset, this.fetchLimit)
        const localLogs = await this.repo.getLogs(offset, this.fetchLimit)
        console.log('offset: %d, remote logs: %d, local logs： %d', offset, remoteOldLogs.length, localLogs.length)
        const pos = this._diff(remoteOldLogs, localLogs)
        console.log('diff pos: %d', pos)
        if (pos === -1 || pos >= localLogs.length) {
          break
        }
        if (offset === 0) {
          const rollbackSeq = -1
          await this.repo.rollbackLogUntil(rollbackSeq)
          this.offset = rollbackSeq + 1
          this._haveRollback = true
          break
        } else {
          const rollbackSeq = localLogs[pos - 1].seq
          await this.repo.rollbackLogUntil(rollbackSeq)
          this.offset = rollbackSeq + 1
          this._haveRollback = true
        }
        offset = Math.max(0, offset - this.fetchLimit)
      }
    }

    // step 2: fetch new logs
    while (true) {
      const logs = await this._fetchRemoteLogs(this.offset, this.fetchLimit)
      console.log('fetch %d logs from offset %d', logs.length, this.offset)
      for (const log of logs) {
        await this.repo.commitLog(log)
      }
      this._fetchCount += logs.length
      this.offset += logs.length
      if (logs.length < this.fetchLimit) {
        break
      }
    }
  }

  async _fetchRemoteLogs(offset, limit) {
    const rawLogs = await this.contract.getPaintLogs(offset, limit).call()
    const logs = []
    if (rawLogs.length > 0) {
      for (let i = 0; i < rawLogs[0].length; i++) {
        logs.push({
          user: this._toAddress(rawLogs[0][i]),
          price: parseInt(rawLogs[1][i].toString()),
          coordinate: Number(rawLogs[2][i]),
          color: Number(rawLogs[3][i]),
          seq: offset + i
        })
      }
    }
    return logs
  }



  /**
   * 
   * @param {Array} a remote logs to diff
   * @param {Array} b local logs to diff
   * @returns {Number} diff position, -1 means no difference
   */
  _diff(a, b) {
    const minLen = Math.min(a.length, b.length)
    for (let i = 0; i < minLen; i++) {
      if (!this._equal(a[i], b[i])) {
        return i
      }
    }
    if (a.length !== b.length) {
      return minLen
    } else {
      return -1
    }
  }

  _equal(l, r) {
    return l.coordinate === r.coordinate &&
      l.color === r.color &&
      l.price === r.price
  }

  _toAddress(addr) {
    return this.tronWeb.address.fromHex('41' + addr.slice(2))
  }
}